package de.baumann.browser.utils.web3j;

import android.util.Log;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthEstimateGas;
import org.web3j.protocol.core.methods.response.EthGasPrice;
import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;

import de.baumann.browser.BuildConfig;
import de.baumann.browser.db.ETHWallet;
import io.reactivex.FlowableOnSubscribe;

public class TokenUtil {
    private static Web3j web3j;

    public static String getPrivateKey(String password, ETHWallet ethWallet) throws IOException, CipherException {
        Credentials credentials = getCredentials(password, ethWallet);
        if (credentials == null) {
            return null;
        }
        return credentials.getEcKeyPair().getPrivateKey().toString(16);
    }

    private static Credentials getCredentials(String password, ETHWallet ethWallet) throws IOException, CipherException {
        return WalletUtils.loadCredentials(password, new File(ethWallet.getKeystorePath()));
    }


    private static Web3j initWeb3j() {
        if (web3j == null) {
            web3j = Web3j.build(new HttpService(BuildConfig.WALLET_NODE_URL));
        }
        return web3j;
    }


    //    获取账户的Nonce
    public static BigInteger getNonce(String addr) throws ExecutionException, InterruptedException {
        EthGetTransactionCount ethGetTransactionCount = initWeb3j().ethGetTransactionCount(
                addr, DefaultBlockParameterName.LATEST).sendAsync().get();
        BigInteger nonce = ethGetTransactionCount.getTransactionCount();
        return nonce;
    }

    //    估算手续费上限
    public static BigInteger getTransactionGasLimit(Transaction transaction) {
        try {
            EthEstimateGas ethEstimateGas = initWeb3j().ethEstimateGas(transaction).send();
            if (ethEstimateGas.hasError()) {
                throw new RuntimeException(ethEstimateGas.getError().getMessage());
            }
            return ethEstimateGas.getAmountUsed();

        } catch (IOException e) {
            throw new RuntimeException("net error");
        }
    }


    public static String transferETH(String fromAddr, String privateKey, String toAddress, String amount, long limit) throws ExecutionException, InterruptedException {

        // 获得nonce
        BigInteger nonce = getNonce(fromAddr);
        // value 转换
        BigInteger value = Convert.toWei(amount, Convert.Unit.ETHER).toBigInteger();

        // gasPrice 转换
        BigInteger price = getGasPrice();

        // 构建交易
        Transaction transaction = Transaction.createEtherTransaction(fromAddr, nonce, price, null, toAddress, value);
        BigInteger gasLimit;
        if (limit == 0) {
            // 计算gasLimit
            gasLimit = getTransactionGasLimit(transaction).multiply(new BigInteger("2"));
        } else {
            gasLimit = BigInteger.valueOf(limit);
        }

        RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
                nonce, price, gasLimit, toAddress, value);

        Credentials credentials = Credentials.create(privateKey);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        String hexValue = Numeric.toHexString(signedMessage);
        EthSendTransaction ethSendTransaction =
                initWeb3j().ethSendRawTransaction(hexValue).sendAsync().get();
        String transactionHash = ethSendTransaction.getTransactionHash();
        Log.d("交易哈希", "---------------" + transactionHash);
        return transactionHash;
    }


    private static BigInteger getGasPrice() {
        try {
            EthGasPrice price = initWeb3j().ethGasPrice().sendAsync().get();
            BigInteger gasPrice = price.getGasPrice();
            return gasPrice;
        } catch (Exception e) {
            Log.e("gasPrice失败:{}", e.getMessage());
        }
        return null;
    }


    public static BigDecimal getBalance(String address) {
        try {
            if (address.startsWith("odx")) {
                address = "0x" + address.substring(3);
            } else if (!address.startsWith("0x")) {
                address = "0x" + address;
            }

            EthGetBalance ethGetBalance = initWeb3j().ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
            return Convert.fromWei(new BigDecimal(ethGetBalance.getBalance()), Convert.Unit.ETHER);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private static Function balanceOf(String owner) {
        return new Function(
                "balanceOf",
                Collections.singletonList(new Address(owner)),
                Collections.singletonList(new TypeReference<Uint256>() {
                }));
    }

    public static BigDecimal getBalance2(String url, String walletAddress, String contractAddress) {
        try {
            Function function = balanceOf(walletAddress);
            String responseValue = callSmartContractFunction(url, function, contractAddress, walletAddress);
            Log.d("1111", "11111----------" + responseValue);
            List<Type> response = FunctionReturnDecoder.decode(
                    responseValue, function.getOutputParameters());
            if (response.size() == 1) {
                return Convert.fromWei(new BigDecimal(((Uint256) response.get(0)).getValue()), Convert.Unit.ETHER);
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    public static String callSmartContractFunction(String url, Function function, String contractAddress, String walletAddress) throws Exception {
        String encodedFunction = FunctionEncoder.encode(function);
        Web3j web3j = Web3j.build(new HttpService((url)));
        EthCall response = web3j.ethCall(
                Transaction.createEthCallTransaction(walletAddress, contractAddress, encodedFunction),
                DefaultBlockParameterName.LATEST)
                 .send();
        return response.getValue();
    }


    //获取地址下的算力
    public static BigDecimal getPower(String node, String contract, String address) {
        if (node == "" || contract == "" || address == "") {
            return new BigDecimal(0.0000);
        }
        if (address.startsWith("odx")) {
            address = "0x" + address.substring(3);
        } else if (!address.startsWith("0x")) {
            address = "0x" + address;
        }
        Function function = balancePowerOf(address);
        Web3j web3j = Web3j.build(new HttpService((node)));
        String encodedFunction = FunctionEncoder.encode(function);
        EthCall ethCall = null;
        try {
            ethCall = web3j.ethCall(Transaction.createEthCallTransaction(address, contract, encodedFunction), DefaultBlockParameterName.LATEST).send();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String value = ethCall.getValue();
        List<Type> response = FunctionReturnDecoder.decode(value, function.getOutputParameters());
        if (response.size() == 1) {
            BigDecimal bigDecimal = new BigDecimal(response.get(0).getValue().toString());
            return bigDecimal;
        } else {
            return new BigDecimal(0.0000);
        }
    }

    private static Function balancePowerOf(String address) {
        return new Function(
                "userPowers",
                Collections.singletonList(new Address(address)),
                Collections.singletonList(new TypeReference<Uint256>() {
                }));
    }

}
